在本系列文中,所有的程式碼以及測試都可以在 should-i-use-fp-ts 找到,今日的範例放在 src/day-07
並且有習題和測試可以讓大家練習。
今天終於開始要使用 fp-ts
中的 modules
了,選擇使用的 module
是 Option
,先來看 Option
中的基本型別。
import * as O from 'fp-ts/lib/Option';
type None = {
readonly _tag: 'None';
}
type Some<A> = {
readonly _tag: 'Some';
readonly value: A;
}
type Option<A> = None | Some<A>;
這邊可以將 Option<A>
理解為一個箱子
,我們會根據操作來決定箱子裡面是有(Some
)還是沒有(None
)所以沒有薛丁格的貓,接下來來看 Option
中的建構子。
const none: Option<never> = <const>({ _tag: 'None' });
const some = <A>(value: A): Some<A> => <const>({ _tag: 'Some', value });
const optionNone = O.none // { _tag: 'None' }
const option1 = O.some(1) // { _tag: 'Some', value: 1 }
const optionOf1 = O.of(1) // { _tag: 'Some', value: 1 }
在 Option
之中,所有不合法的元素都可以以 O.none
的形式來表現,這樣做可以統一對於不合法元素的處理(ex. string: "" => none, number: 0 => none, array: [] => none)。
這個特性有助於改寫 if...else
的判斷式,以下用 head
(取 array 的首位)以及 inverse
(倒數)為例。
/** Imperative */
const headI = <A>(xs: ReadonlyArray<A>) => {
if (xs.length === 0) throw new Error('empty array');
return xs[0];
};
const inverseI = (x: number) => {
if (x === 0) throw new Error('cannot divide by zero');
return 1 / x;
};
/** fp-ts */
type Head = <A>(xs: ReadonlyArray<A>) => O.Option<A>;
type Inverse = (x: number) => O.Option<number>;
const head: Head = xs => xs.length === 0 ? O.none : O.some(xs[0]);
const inverse: Inverse = x => x === 0 ? O.none : O.some(1 / x);
這邊將 Imperative
中的非法操作直接拋出 Error
,而 fp-ts
這邊則是將非法操作統一設置為 O.none = { _tag: 'None' }
,這樣設計有助於後續操作失敗時可以產出統一的行為,這邊給出簡單的例子。
const getHeadI = (xs: ReadonlyArray<number>) => {
try {
return `result is ${headI(xs)}`;
} catch {
return 'no result';
}
}
const getHeadO = (xs: ReadonlyArray<number>) =>
head(xs) === O.none ? 'no result' : `result is ${headI(xs)}`;
可能會有人說,用三元運算子本身還是 if...else
阿,根本沒用老子不學了。
這是由於今天還沒有講解 Option
中取用值以及運算的函式,明天學到 map
, flatMap
, match
三個函式之後這段操作可以完全不使用三元運算子,而且可以在一個 pipe
之中完成需求。
今天的主題在 should-i-use-fp-ts 在 src/day-07
並且有習題和測試可以讓大家練習。